// Copyright (C) 2012 Volkov Zachar
// This file is part of the "zAxis Game Engine".
// For conditions of distribution and use, see copyright notice zx_Common.hpp

#include "zx_Video.hpp"
#include "gl/glew.h"
#include "gl/wglew.h"

namespace zx {

static const uint8 _warning_texture_8x8[] = 
{
     0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 
     0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff,
     0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 
     0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff,
     0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 
     0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff,
     0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 
     0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff,
     0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff,
     0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 
     0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff,
     0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 
     0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff,
     0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 
     0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff, 0x00,0x00,0x00,0xff,
     0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 0xff,0xa5,0x00,0xff, 
};

static const GLenum gl_wrap[] = 
{
    GL_REPEAT,          // TW_WRAP,
    GL_MIRRORED_REPEAT, // TW_MIRROR,
    GL_CLAMP_TO_EDGE,   // TW_CLAMP,
    GL_CLAMP_TO_BORDER, // TW_BORDER,
};

#define _MIN_FILTER 0
#define _MAG_FILTER 1
#define _MIN_FILTER_RECT 2

static const GLenum gl_filter[5][3] = // min, mag, min for rect
{
     { GL_NEAREST, GL_NEAREST, GL_NEAREST },   // TF_NEAREST,
     { GL_LINEAR, GL_LINEAR, GL_NEAREST },     // TF_LINEAR,
     { GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR, GL_LINEAR }, // TF_BILINEAR,
     { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_LINEAR },  // TF_TRILINEAR,
     { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_LINEAR }, // TF_ANISOTROPIC,
};

static const GLenum gl_polygone_mode [] = 
{
     GL_FILL,  // PM_SOLID = 0,
     GL_LINE,  // PM_WIREFRAME = 1,
     GL_POINT, // PM_POINTS = 2, 
};

static const GLenum gl_blend_factor [] = 
{
     GL_ZERO, // BF_ZERO = 0,	
     GL_ONE, // BF_ONE,	
     GL_DST_COLOR, // BF_DST_COLOR, 	
     GL_SRC_COLOR, // BF_SRC_COLOR, 	
     GL_ONE_MINUS_DST_COLOR, // BF_ONE_MINUS_DST_COLOR, 	
     GL_ONE_MINUS_SRC_COLOR, // BF_ONE_MINUS_SRC_COLOR, 	
     GL_DST_ALPHA, // BF_DST_ALPHA, 	
     GL_SRC_ALPHA, // BF_SRC_ALPHA, 	
     GL_ONE_MINUS_DST_ALPHA, // BF_ONE_MINUS_DST_ALPHA, 	
     GL_ONE_MINUS_SRC_ALPHA, // BF_ONE_MINUS_SRC_ALPHA, 
};

static const GLenum gl_blend_operation [] = 
{
     GL_FUNC_ADD, // BO_ADD = 0,
     GL_FUNC_SUBTRACT, // BO_SUBTRACT,
     GL_FUNC_REVERSE_SUBTRACT, // BO_REVERSE_SUBTRACT,
     GL_MIN, // BO_MIN,
     GL_MAX, // BO_MAX,
};

static const GLenum gl_compare_func [] =
{
     GL_NEVER, // CF_NEVER = 0,
     GL_LESS, // CF_LESS,
     GL_LEQUAL, // CF_LEQUAL,
     GL_EQUAL, // CF_EQUAL,
     GL_NOTEQUAL, // CF_NEQUAL,
     GL_GEQUAL, // CF_GEQUAL,
     GL_GREATER, // CF_GREATER,
     GL_ALWAYS, // CF_ALWAYS,
};

static const GLenum gl_stencil_op [] = 
{
     GL_KEEP, // SO_KEEP = 0,
     GL_ZERO, // SO_ZERO,
     GL_REPLACE, // SO_REPLACE,
     GL_INCR, // SO_INCREMENT,
     GL_DECR, // SO_DECREMENT,
     GL_INCR_WRAP_EXT, // SO_INCREMENT_WRAP,
     GL_DECR_WRAP_EXT, // SO_DECREMENT_WRAP,
     GL_INVERT, // SO_INVERT,
};

static const GLenum gl_back_stencil_op [] = 
{
     GL_KEEP, // SO_KEEP = 0,
     GL_ZERO, // SO_ZERO,
     GL_REPLACE, // SO_REPLACE,
     GL_DECR, // SO_INCREMENT,
     GL_INCR, // SO_DECREMENT,
     GL_DECR_WRAP_EXT, // SO_INCREMENT_WRAP,
     GL_INCR_WRAP_EXT, // SO_DECREMENT_WRAP,
     GL_INVERT, // SO_INVERT,
};

static const GLenum gl_primitive_type[] = 
{
     GL_POINTS, // PT_POINT,
     GL_POINTS, // PT_POINT_SPRITE,
     GL_LINES,  // PT_LINE_LIST,
     GL_LINE_STRIP, // PT_LINE_STRIP,
     GL_TRIANGLES, // PT_TRIANGLE_LIST,
     GL_TRIANGLE_STRIP, // PT_TRIANGLE_STRIP,
     GL_QUADS, // PT_QUADS,
     GL_QUAD_STRIP, // PT_QUAD_STRIP,
};

static const GLenum gl_texture_type[] = 
{
     GL_TEXTURE_1D, // TT_1D = 0,
     GL_TEXTURE_2D, // TT_2D = 1,
     GL_TEXTURE_3D, // TT_3D = 2,
     GL_TEXTURE_RECTANGLE_ARB,// TT_RECT = 3,
     GL_TEXTURE_CUBE_MAP, // TT_CUBE = 4,
};

static const GLenum gl_texture_binding[] = 
{
     GL_TEXTURE_BINDING_1D, // TT_1D = 0,
     GL_TEXTURE_BINDING_2D, // TT_2D = 1,
     GL_TEXTURE_BINDING_3D, // TT_3D = 2,
     GL_TEXTURE_BINDING_RECTANGLE_ARB,// TT_RECT = 3,
     GL_TEXTURE_BINDING_CUBE_MAP, // TT_CUBE = 4,
};

static const GLenum gl_buffer_usage[] = 
{
     GL_DYNAMIC_DRAW, // BU_STATIC,  
     GL_DYNAMIC_COPY, // BU_DYNAMIC,
     GL_STREAM_DRAW,  // BU_STREAM, 

};

static const GLenum gl_access_mode[] = 
{
     GL_NONE, // AM_NONE  
     GL_READ_ONLY, // AM_READ_ONLY  
     GL_WRITE_ONLY, // AM_WRITE_ONLY,
     GL_READ_WRITE,  // AM_READ_WRITE, 
};

//----------------------------------------------------------------------------//
// ctor, dtor
//----------------------------------------------------------------------------//
VideoDriver::VideoDriver( void ) : m_logger( Logger::CreateInstance() )
{
     GLenum _glew_err = glewInit();

     //

     Logger::Info( "VideoDriver: Startup..." );

     if( _glew_err != GLEW_OK )
     {
          Logger::Error( Format( "VideoDriver: Startup: Error: %s", glewGetErrorString( _glew_err ) ) );
     }

     uint _count_extensions = 0;
     for( const char* _exts = (const char*)glGetString( GL_EXTENSIONS ); *_exts; ++ _exts )
         { if( *_exts == ' ' ) { ++ _count_extensions; } }
     for( const char* _exts = (const char*)wglGetExtensionsStringEXT(); *_exts; ++ _exts )
         { if( *_exts == ' ' ) { ++ _count_extensions; } }

     Logger::Info( Format( "VideoDriver: Device info:\n    %s %s\n    OpenGL %s\n    GLSL %s\n    ~%d extensions", glGetString( GL_VENDOR ), glGetString( GL_RENDERER ), glGetString( GL_VERSION ), glGetString( GL_SHADING_LANGUAGE_VERSION_ARB ), _count_extensions ) );


     //
     _InitFeatures();

     //

     m_warning_texture[TT_1D] = new Texture( TT_1D, PF_RGBA8, 8 );
     m_warning_texture[TT_2D] = new Texture( TT_2D, PF_RGBA8, 8, 8, 1, _warning_texture_8x8 );
     m_warning_texture[TT_3D] = new Texture( TT_1D, PF_RGBA8, 8, 8, 8 );
     m_warning_texture[TT_RECT] = new Texture( TT_RECT, PF_RGBA8, 8, 8, 1, _warning_texture_8x8 );
     m_warning_texture[TT_CUBE] = new Texture( TT_CUBE, PF_RGBA8, 8, 8 );

     zxFixMe( "init warning textures" )

     m_active_texture_unit = -1;
     m_used_texture_units = 0;
     for( uint i = 0; i < 15; ++ i )
     {
          m_texture_unit[i].point_sprites_enabled = false;
          for( uint j = 0; j < 5; ++ j )
          {
               m_texture_unit[i].anisotropy[j] = 1;
               m_texture_unit[i].wrap_s[j] = (TextureWrap)-1;
               m_texture_unit[i].wrap_t[j] = (TextureWrap)-1;
               m_texture_unit[i].wrap_r[j] = (TextureWrap)-1;
               m_texture_unit[i].min_filter[j] = (TextureFilter)-1;
               m_texture_unit[i].mag_filter[j] = (TextureFilter)-1;
          }
          //glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE );
     }

          

     m_color_mask = CC_RGBA;
     m_depth_mask = true;
     m_stencil_mask = 0xff;

     m_point_size = 1;
     m_line_width = 1;

     glGetIntegerv( GL_VIEWPORT, m_main_vp );
     glGetIntegerv( GL_VIEWPORT, m_current_vp );
     m_ortho2d.MakeOrtho2D( m_main_vp[2], m_main_vp[3] );

     //fsquad
     m_fsquad = new VertexBuffer( 4 );

     Vertex* _fsquad = m_fsquad -> Lock( AM_WRITE_ONLY );

     _fsquad[0].position = Vector3( -1.0, +1.0, 0.0 );
     _fsquad[0].texcoord = Vector2( 0, 1 );
     _fsquad[1].position = Vector3( -1.0, -1.0, 0.0 );
     _fsquad[1].texcoord = Vector2( 0, 0 );
     _fsquad[2].position = Vector3( +1.0, +1.0, 0.0 );
     _fsquad[2].texcoord = Vector2( 1, 1 );
     _fsquad[3].position = Vector3( +1.0, -1.0, 0.0 );
     _fsquad[3].texcoord = Vector2( 1, 0 );

     m_fsquad -> Unlock();

     m_2d_mode = true;
     glEnableVertexAttribArray( VA_POSITION );
     glEnableVertexAttribArray( VA_DIFFUSE );
     glEnableVertexAttribArray( VA_TEXCOORD );

     m_point_sprites_enabled = false;
}
//----------------------------------------------------------------------------//
VideoDriver::~VideoDriver( void )
{
     Logger::Info( "VideoDriver: Shutdown..." );
     glUseProgramObjectARB( 0 );
     glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 ); // ?
}
//----------------------------------------------------------------------------//
void VideoDriver::_InitFeatures( void )
{
     m_max_viewport_size[0] = 0;
     m_max_viewport_size[1] = 0;
     m_max_texture_size[ TT_1D ] = 0; 
     m_max_texture_size[ TT_2D ] = 0; 
     m_max_texture_size[ TT_3D ] = 0; 
     m_max_texture_size[ TT_RECT ] = 0; 
     m_max_texture_size[ TT_CUBE ] = 0; 
     m_max_anisotropy = 1;
     m_max_renderbuffer_size = 0;
     m_max_msaa = 0;
     m_glsl_version = 0;

     // extensions presence
     m_extensions[VDE_CORE_FUNCTIONAL_1_0] = GLEW_VERSION_1_1 != 0;
     m_extensions[VDE_CORE_FUNCTIONAL_1_1] = GLEW_VERSION_1_1 != 0;
     m_extensions[VDE_CORE_FUNCTIONAL_1_2] = GLEW_VERSION_1_2 != 0;
     m_extensions[VDE_CORE_FUNCTIONAL_1_3] = GLEW_VERSION_1_3 != 0;
     m_extensions[VDE_CORE_FUNCTIONAL_1_4] = GLEW_VERSION_1_4 != 0;
     m_extensions[VDE_CORE_FUNCTIONAL_1_5] = GLEW_VERSION_1_5 != 0;
     m_extensions[VDE_CORE_FUNCTIONAL_2_0] = GLEW_VERSION_2_0 != 0;

     m_extensions[VDE_FRAMEBUFFER] = GLEW_EXT_framebuffer_object != 0;
     m_extensions[VDE_FRAMEBUFFER_BLIT] = GLEW_EXT_framebuffer_blit != 0;
     m_extensions[VDE_FRAMEBUFFER_MULTISAMPLE] = GLEW_EXT_framebuffer_multisample != 0;
     m_extensions[VDE_TEXTURE_FILTER_ANISOTROPIC] = GLEW_EXT_texture_filter_anisotropic != 0;
     m_extensions[VDE_TEXTURE_RECT] = GLEW_ARB_texture_rectangle != 0;
     m_extensions[VDE_TEXTURE_INTERNAL_FORMAT_DXT] = GLEW_EXT_texture_compression_s3tc != 0;
     m_extensions[VDE_TEXTURE_INTERNAL_FORMAT_D24S8] = GLEW_EXT_packed_depth_stencil != 0;
     m_extensions[VDE_TEXTURE_INTERNAL_FORMAT_FLOAT] = GLEW_ARB_texture_float != 0;
     m_extensions[VDE_TEXTURE_RECT_INTERNAL_FORMAT_FLOAT] = GLEW_NV_float_buffer || GLEW_ARB_texture_float;
     m_extensions[VDE_STENCIL_TWO_SIDED_OPERATION] = GLEW_EXT_stencil_two_side != 0,
     m_extensions[VDE_VERTEX_SHADER] = GLEW_ARB_shader_objects && GLEW_ARB_shading_language_100 && GLEW_ARB_vertex_shader;
     m_extensions[VDE_FRAGMENT_SHADER] = GLEW_ARB_shader_objects && GLEW_ARB_shading_language_100 && GLEW_ARB_fragment_shader;
     m_extensions[VDE_OCCLUSION_QUERY] = GLEW_ARB_occlusion_query != 0;                     
     
     m_extensions[VDE_MINIMAL] = m_extensions[VDE_CORE_FUNCTIONAL_1_0] &&
                                 m_extensions[VDE_CORE_FUNCTIONAL_1_1] &&
                                 m_extensions[VDE_CORE_FUNCTIONAL_1_2] &&
                                 m_extensions[VDE_CORE_FUNCTIONAL_1_3] &&
                                 m_extensions[VDE_CORE_FUNCTIONAL_1_4] &&
                                 m_extensions[VDE_CORE_FUNCTIONAL_1_5] &&
                                 m_extensions[VDE_CORE_FUNCTIONAL_2_0] &&
                                 m_extensions[VDE_FRAMEBUFFER] &&
                                 m_extensions[VDE_FRAMEBUFFER_BLIT] &&
                                 //m_extensions[VDE_FRAMEBUFFER_MULTISAMPLE] &&
                                 m_extensions[VDE_TEXTURE_RECT] &&
                                 m_extensions[VDE_TEXTURE_FILTER_ANISOTROPIC] &&
                                 m_extensions[VDE_TEXTURE_INTERNAL_FORMAT_DXT] &&
                                 m_extensions[VDE_TEXTURE_INTERNAL_FORMAT_D24S8] &&
                                 //m_extensions[VDE_TEXTURE_INTERNAL_FORMAT_FLOAT] &&
                                 //m_extensions[VDE_TEXTURE_RECT_INTERNAL_FORMAT_FLOAT] &&
                                 m_extensions[VDE_STENCIL_TWO_SIDED_OPERATION] &&
                                 m_extensions[VDE_VERTEX_SHADER] &&
                                 m_extensions[VDE_FRAGMENT_SHADER] &&
                                 m_extensions[VDE_OCCLUSION_QUERY] &&
                                 true;

     m_extensions[VDE__TEXTURE_RECT_INTERNAL_FORMAT_FLOAT_ARB] = GLEW_ARB_texture_float != 0;
     m_extensions[VDE__TEXTURE_RECT_INTERNAL_FORMAT_FLOAT_NV] = GLEW_NV_float_buffer != 0;
     //m_extensions[VDE__FRAMEBUFFER_MULTISAMPLE_EXT] = GLEW_EXT_framebuffer_multisample != 0;
     //m_extensions[VDE__FRAMEBUFFER_MULTISAMPLE_NV] = GLEW_NV_framebuffer_multisample_coverage != 0;

     //

     String _msg;
     _msg = "VideoDriver: Extensions:";
     _msg += Format( "\n    * Core functional 1.0: %s", zxChoose( m_extensions[VDE_CORE_FUNCTIONAL_1_0], "Yes", "No" ) );
     _msg += Format( "\n    * Core functional 1.1: %s", zxChoose( m_extensions[VDE_CORE_FUNCTIONAL_1_1], "Yes", "No" ) );
     _msg += Format( "\n    * Core functional 1.2: %s", zxChoose( m_extensions[VDE_CORE_FUNCTIONAL_1_2], "Yes", "No" ) );
     _msg += Format( "\n    * Core functional 1.3: %s", zxChoose( m_extensions[VDE_CORE_FUNCTIONAL_1_3], "Yes", "No" ) );
     _msg += Format( "\n    * Core functional 1.4: %s", zxChoose( m_extensions[VDE_CORE_FUNCTIONAL_1_4], "Yes", "No" ) );
     _msg += Format( "\n    * Core functional 1.5: %s", zxChoose( m_extensions[VDE_CORE_FUNCTIONAL_1_4], "Yes", "No" ) );
     _msg += Format( "\n    * Core functional 2.0: %s", zxChoose( m_extensions[VDE_CORE_FUNCTIONAL_1_4], "Yes", "No" ) );

     _msg += Format( "\n    * Rectangle texture: %s", zxChoose( m_extensions[VDE_TEXTURE_RECT], "Yes", "No" ) );
     _msg += Format( "\n    * Texture filter anisotropic: %s", zxChoose( m_extensions[VDE_TEXTURE_FILTER_ANISOTROPIC], "Yes", "No" ) );
     _msg += Format( "\n    * Texture internal format (DXTx): %s", zxChoose( m_extensions[VDE_TEXTURE_INTERNAL_FORMAT_DXT], "Yes", "No" ) );
     _msg += Format( "\n    * Texture internal format (D24S8): %s", zxChoose( m_extensions[VDE_TEXTURE_INTERNAL_FORMAT_D24S8], "Yes", "No" ) );
     _msg += Format( "\n    ~ Texture internal format (FLOAT): %s", zxChoose( m_extensions[VDE_TEXTURE_INTERNAL_FORMAT_FLOAT], "Yes", "No" ) );
     _msg += Format( "\n    ~ TextureRect internal format (FLOAT): %s", zxChoose( m_extensions[VDE_TEXTURE_RECT_INTERNAL_FORMAT_FLOAT], "Yes", "No" ) );
     _msg += Format( "\n    * Stencil two-sided operation: %s", zxChoose( m_extensions[VDE_STENCIL_TWO_SIDED_OPERATION], "Yes", "No" ) );
     
     _msg += Format( "\n    * Vertex shader: %s", zxChoose( m_extensions[VDE_VERTEX_SHADER], "Yes", "No" ) );
     _msg += Format( "\n    * Fragment shader: %s", zxChoose( m_extensions[VDE_FRAGMENT_SHADER], "Yes", "No" ) );
     _msg += Format( "\n    * Framebuffer: %s", zxChoose( m_extensions[VDE_FRAMEBUFFER], "Yes", "No" ) );
     _msg += Format( "\n    * Framebuffer blit: %s", zxChoose( m_extensions[VDE_FRAMEBUFFER_BLIT], "Yes", "No" ) );
     _msg += Format( "\n    ~ Framebuffer multisample: %s", zxChoose( m_extensions[VDE_FRAMEBUFFER_MULTISAMPLE], "Yes", "No" ) );
     _msg += Format( "\n    * Occlusion query: %s", zxChoose( m_extensions[VDE_OCCLUSION_QUERY], "Yes", "No" ) );
     Logger::Info( _msg );

     if( !m_extensions[VDE_MINIMAL] )
     {
          Logger::Error( "VideoDriver: Device does not support minimum extensions" );
          return;
     }

     GLint _ip[4];
          
     glGetIntegerv( GL_MAX_VIEWPORT_DIMS, _ip );
     m_max_viewport_size[0] = _ip[0];
     m_max_viewport_size[1] = _ip[1];

     glGetIntegerv( GL_MAX_TEXTURE_SIZE, _ip );
     m_max_texture_size[ TT_1D ] = _ip[0]; 
     m_max_texture_size[ TT_2D ] = _ip[0]; 

     glGetIntegerv( GL_MAX_3D_TEXTURE_SIZE, _ip );
     m_max_texture_size[ TT_3D ] = _ip[0]; 
          
     glGetIntegerv( GL_MAX_RECTANGLE_TEXTURE_SIZE_ARB, _ip );
     m_max_texture_size[ TT_RECT ] = _ip[0]; 
          
     glGetIntegerv( GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB, _ip );
     m_max_texture_size[ TT_CUBE ] = _ip[0]; 

     glGetIntegerv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, _ip );
     m_max_anisotropy = _ip[0]; 

     glGetIntegerv( GL_MAX_RENDERBUFFER_SIZE_EXT, _ip );
     m_max_renderbuffer_size = _ip[0];
                   
     glGetIntegerv( GL_MAX_SAMPLES_EXT, _ip );
     m_max_msaa = _ip[0];

     sscanf( (const char*)glGetString( GL_SHADING_LANGUAGE_VERSION_ARB ), "%1d.%2d", &_ip[0], &_ip[1] );
     m_glsl_version = _ip[0]*100 + _ip[1];
     //

     _msg = "VideoDriver: Features:";

     _msg += Format( "\n    * Max viewport size: %u x %u", m_max_viewport_size[0], m_max_viewport_size[1] );
     _msg += Format( "\n    * Max Texture1D size: %u", m_max_texture_size[ TT_1D ] );
     _msg += Format( "\n    * Max Texture2D size: %u x %u", m_max_texture_size[ TT_2D ], m_max_texture_size[ TT_2D ] );
     _msg += Format( "\n    * Max Texture3D size: %u x %u x %u", m_max_texture_size[ TT_3D ], m_max_texture_size[ TT_3D ], m_max_texture_size[ TT_3D ] );
     _msg += Format( "\n    * Max TextureRect size: %u x %u", m_max_texture_size[ TT_RECT ], m_max_texture_size[ TT_RECT ] );
     _msg += Format( "\n    * Max TextureCube size: %u x %u", m_max_texture_size[ TT_CUBE ], m_max_texture_size[ TT_CUBE ] );
     _msg += Format( "\n    * Max anisotropic texture filtering: %d", m_max_anisotropy );
     _msg += Format( "\n    * Max RenderTarget size: %u x %u", m_max_renderbuffer_size, m_max_renderbuffer_size );
     _msg += Format( "\n    * Max RenderBuffer size: %u x %u", m_max_renderbuffer_size, m_max_renderbuffer_size );
     _msg += Format( "\n    * Max samples (MSAA): %d", m_max_msaa );
     _msg += Format( "\n    * GLSL version: %d.%d", m_glsl_version / 100, m_glsl_version % 100 );
     
     //
     //glGetIntegerv( GL_MAX_COLOR_ATTACHMENTS_EXT, _ip );
     //_msg += Format( "\n    * Max color attachments: %u ", _ip[0] );
     //glGetIntegerv( GL_SHADING_LANGUAGE_VERSION_ARB, _ip );
     //_msg += Format( "\n    * GLSL version: %u ", _ip[0] );
                                    
     Logger::Info( _msg );
}
//----------------------------------------------------------------------------//
// convert zx -> gl
//----------------------------------------------------------------------------//
uint VideoDriver::_glBufferUsage( BufferUsage _usage )
{
     return gl_buffer_usage[ _usage ];
}
//----------------------------------------------------------------------------//
uint VideoDriver::_glAccessMode( AccessMode _access )
{
     return gl_access_mode[ _access ];
}
//----------------------------------------------------------------------------//
uint VideoDriver::_glTextureType( TextureType _type )
{
     return gl_texture_type[ _type ];
}
//----------------------------------------------------------------------------//
uint VideoDriver::_glTextureInternalFormat( TextureType _tt, PixelFormat _pf )
{
     switch( _pf )
     {
          case PF_UNKNOWN : return GL_NONE;
          case PF_A8 : return GL_ALPHA8;
          case PF_RGB8 : return GL_RGB8;
          case PF_RGBA8 : return GL_RGBA8;

          case PF_RGB16F :
          {
               if( GLEW_VERSION_3_0 || GLEW_ARB_texture_float ) { return GL_RGB16F_ARB; }
               else if( _tt == TT_RECT && GLEW_NV_float_buffer ) { return GL_FLOAT_RGB16_NV; }

          } return GL_RGB8;

          case PF_RGBA16F : 
          {
               if( GLEW_VERSION_3_0 || GLEW_ARB_texture_float ) { return GL_RGBA16F_ARB; }
               else if( _tt == TT_RECT && GLEW_NV_float_buffer ) { return GL_FLOAT_RGBA16_NV; }

          } return GL_RGBA8;

          case PF_RGB32F : 
          {
               if( GLEW_VERSION_3_0 || GLEW_ARB_texture_float ) { return GL_RGB32F_ARB; }
               else if( _tt == TT_RECT && GLEW_NV_float_buffer ) { return GL_FLOAT_RGB32_NV; }

          } return GL_RGB8;

          case PF_RGBA32F : 
          {
               if( GLEW_VERSION_3_0 || GLEW_ARB_texture_float ) { return GL_RGBA32F_ARB; }
               else if( _tt == TT_RECT && GLEW_NV_float_buffer ) { return GL_FLOAT_RGBA32_NV; }

          } return GL_RGBA8;

          case PF_DXT1 : return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; // rgb ar rgba ?
          case PF_DXT3 : return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
          case PF_DXT5 : return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
          case PF_D24S8 : return GL_DEPTH24_STENCIL8_EXT;
          
     };

     assert( false && "unknown enum" );
     return GL_NONE;
}
//----------------------------------------------------------------------------//
uint VideoDriver::_glTextureFormat( PixelFormat _pf )
{
     switch( _pf )
     {
          case PF_UNKNOWN : return GL_NONE;
          case PF_A8 : return GL_ALPHA;
          case PF_RGB8 : return GL_RGB;
          case PF_RGBA8 : return GL_RGBA;
          case PF_RGB16F : return GL_RGB;
          case PF_RGBA16F : return GL_RGBA;
          case PF_RGB32F : return GL_RGB;
          case PF_RGBA32F : return GL_RGBA;
          case PF_DXT1 : return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; 
          case PF_DXT3 : return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
          case PF_DXT5 : return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
          case PF_D24S8 : return GL_DEPTH_STENCIL_EXT;
     };

     assert( false && "unknown enum" );
     return GL_NONE;
}
//----------------------------------------------------------------------------//
uint VideoDriver::_glTextureDataType( PixelFormat _pf )
{
     switch( _pf )
     {
          case PF_UNKNOWN : return GL_NONE;
          case PF_A8 : return GL_UNSIGNED_BYTE;
          case PF_RGB8 : return GL_UNSIGNED_BYTE;
          case PF_RGBA8 : return GL_UNSIGNED_INT_8_8_8_8_REV;
          case PF_RGB16F : return GL_HALF_FLOAT;
          case PF_RGBA16F : return GL_HALF_FLOAT;
          case PF_RGB32F : return GL_FLOAT;
          case PF_RGBA32F : return GL_FLOAT;
          case PF_DXT1 : return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; 
          case PF_DXT3 : return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
          case PF_DXT5 : return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
          case PF_D24S8 : return GL_UNSIGNED_INT_24_8_EXT;
     };

     assert( false && "unknown enum" );
     return GL_NONE;
}
//----------------------------------------------------------------------------//
uint VideoDriver::_glTextureType( TextureType _type, uint _z )
{
     if( _type == TT_CUBE )
     {
          if( _z < 6 )
          {
               return GL_TEXTURE_CUBE_MAP_POSITIVE_X + _z;
          }

          assert( false && "invalid texture face" );
     }

     return _glTextureType( _type );
}
//----------------------------------------------------------------------------//
uint VideoDriver::_glTextureBinding( TextureType _type )
{
     return gl_texture_binding[ _type ];
}
//----------------------------------------------------------------------------//
// interface
//----------------------------------------------------------------------------//
void VideoDriver::ClearFrameBuffer( uint32 _buffers, const Color& _color, float _depth, uint8 _stencil )
{
     Instance()._ClearFrameBuffer_( _buffers, _color, _depth, _stencil );
}
//----------------------------------------------------------------------------//
void VideoDriver::ClearColorBuffer( const Color& _color ) 
{ 
     ClearFrameBuffer( FBT_COLOR, _color );
}
//----------------------------------------------------------------------------//
void VideoDriver::ClearDepthBuffer( float _depth ) 
{ 
     ClearFrameBuffer( FBT_DEPTH, Color::Black, _depth );
}
//----------------------------------------------------------------------------//
void VideoDriver::ClearStencilBuffer( uint8 _stencil ) 
{ 
     ClearFrameBuffer( FBT_STENCIL, Color::Black, 1.0f, _stencil ); 
}
//----------------------------------------------------------------------------//
void VideoDriver::SetColorMask( uint32 _mask ) 
{ 
     Instance()._SetColorMask_( _mask ); 
}
//----------------------------------------------------------------------------//
void VideoDriver::SetFrontFace( FrontFace _face )
{ 
     Instance()._SetFrontFace_( _face ); 
}
//----------------------------------------------------------------------------//
void VideoDriver::SetCullFace( Face _cull ) 
{ 
     Instance()._SetCullFace_( _cull );
}
//----------------------------------------------------------------------------//
void VideoDriver::SetPolygonMode( Face _face, PolygonMode _mode ) 
{ 
     Instance()._SetPolygonMode_( _face, _mode );
}
//----------------------------------------------------------------------------//
void VideoDriver::SetPointSize( uint _size ) 
{ 
     Instance()._SetPointSize_( _size ); 
}
//----------------------------------------------------------------------------//
void VideoDriver::SetLineWidth( uint _width ) 
{ 
     Instance()._SetLineWidth_( _width );
}
//----------------------------------------------------------------------------//
void VideoDriver::SetProgram( ProgramPtr _program ) 
{ 
     Instance()._SetProgram_( _program );
}
//----------------------------------------------------------------------------//
void VideoDriver::SetRenderTarget( RenderTargetPtr _target ) 
{ 
     Instance()._SetRenderTarget_( _target ); 
} 
//----------------------------------------------------------------------------//
void VideoDriver::SetBlending( bool _enabled, BlendFactor _src_factor, BlendFactor _dst_factor, BlendOperation _op )
{
     Instance()._SetBlending_( _enabled, _src_factor, _dst_factor, _op );
}
//----------------------------------------------------------------------------//
void VideoDriver::SetDepthTest( bool _enabled, bool _write, CompareFunction _fn )
{
     Instance()._SetDepthTest_( _enabled, _write, _fn );
}
//----------------------------------------------------------------------------//
void VideoDriver::SetAlphaTest( bool _enabled, CompareFunction _fn, float _ref, bool _alpha_to_coverage )
{
     Instance()._SetAlphaTest_( _enabled, _fn, _ref, _alpha_to_coverage );
}
//----------------------------------------------------------------------------//
void VideoDriver::SetStencilTest( bool _enabled, CompareFunction _fn, uint8 _ref, uint8 _mask, StencilOperation _stencil_fail, StencilOperation _depth_fail, StencilOperation _pass, bool _two_sided_operation )
{
     Instance()._SetStencilTest_( _enabled, _fn, _ref, _mask, _stencil_fail, _depth_fail, _pass, _two_sided_operation );
}
//----------------------------------------------------------------------------//
void VideoDriver::Render( PrimitiveType _type, VertexBufferPtr _vertices, uint _count_of_vertices, uint _first_vertex )
{
     Instance()._Render_( _type, _vertices, _count_of_vertices, _first_vertex );
}
//----------------------------------------------------------------------------//
void VideoDriver::Render( PrimitiveType _type, VertexBufferPtr _vertices, IndexBufferPtr _indices, uint _count_of_indices, uint _first_vertex, uint _first_index )
{
     Instance()._Render_( _type, _vertices, _indices, _count_of_indices, _first_vertex, _first_index );
}
//----------------------------------------------------------------------------//
void VideoDriver::Render( PrimitiveType _type, const Vertex* _vertices, uint _count_of_vertices, uint _first_vertex )
{
     Instance()._Render_( _type, _vertices, _count_of_vertices, _first_vertex );
}
//----------------------------------------------------------------------------//
void VideoDriver::Render( PrimitiveType _type, const Vertex* _vertices, const uint32* _indices, uint _count_of_indices, uint _first_vertex, uint _first_index )
{
     Instance()._Render_( _type, _vertices, _indices, _count_of_indices, _first_vertex, _first_index );
}
//----------------------------------------------------------------------------//
void VideoDriver::Render( PrimitiveType _type, const vector< Vertex >& _vertices, uint _count_of_vertices, uint _first_vertex )
{
     Instance()._Render_( _type, _vertices, _count_of_vertices, _first_vertex );
}
//----------------------------------------------------------------------------//
void VideoDriver::Render( PrimitiveType _type, const vector< Vertex >& _vertices, const vector< uint32 >& _indices, uint _count_of_indices, uint _first_vertex, uint _first_index )
{
     Instance()._Render_( _type, _vertices, _indices, _count_of_indices, _first_vertex, _first_index );
}
//----------------------------------------------------------------------------//
void VideoDriver::Render( PrimitiveType _type, const Vertex2D* _vertices, uint _count_of_vertices, uint _first_vertex )
{
     Instance()._Render_( _type, _vertices, _count_of_vertices, _first_vertex );
}
//----------------------------------------------------------------------------//
void VideoDriver::Render( PrimitiveType _type, const Vertex2D* _vertices, const uint32* _indices, uint _count_of_indices, uint _first_vertex, uint _first_index )
{
     Instance()._Render_( _type, _vertices, _indices, _count_of_indices, _first_vertex, _first_index );
}
//----------------------------------------------------------------------------//
void VideoDriver::Render( PrimitiveType _type, const vector< Vertex2D >& _vertices, uint _count_of_vertices, uint _first_vertex )
{
     Instance()._Render_( _type, _vertices, _count_of_vertices, _first_vertex );
}
//----------------------------------------------------------------------------//
void VideoDriver::Render( PrimitiveType _type, const vector< Vertex2D >& _vertices, const vector< uint32 >& _indices, uint _count_of_indices, uint _first_vertex, uint _first_index )
{
     Instance()._Render_( _type, _vertices, _indices, _count_of_indices, _first_vertex, _first_index );
}
//----------------------------------------------------------------------------//
void VideoDriver::RenderFullscreenQuad( void )
{
     Instance()._RenderFullscreenQuad_();
}
//----------------------------------------------------------------------------//
void VideoDriver::BeginScene( void )
{
     Instance()._BeginScene_();
}
//----------------------------------------------------------------------------//
void VideoDriver::EndScene( void )
{
     Instance()._EndScene_();
}
//----------------------------------------------------------------------------//
void VideoDriver::SetMainViewport( int _px, int _py, int _sx, int _sy )
{
     Instance()._SetMainViewport_( _px, _py, _sx, _sy );
}

void VideoDriver::SetVSyncInterval( uint _interval )
{
     if( _interval > 5 )
     {
          _interval = 5;
     }

     wglSwapIntervalEXT( (int)_interval );
}

uint VideoDriver::GetVSyncInterval( void )
{
     return (uint)wglGetSwapIntervalEXT();
}

//----------------------------------------------------------------------------//
// render state
//----------------------------------------------------------------------------//
void VideoDriver::_ClearFrameBuffer_( uint32 _buffers, const Color& _color, float _depth, uint8 _stencil )
{
     GLbitfield _flags = 0;

     if( _buffers & FBT_COLOR )
     {
          _flags |= GL_COLOR_BUFFER_BIT;

          if( m_color_mask != CC_RGBA )
          {
               glColorMask( true, true, true, true );
          }

          Vector4 _cf = _color;
          glClearColor( _cf.x, _cf.y, _cf.z, _cf.w );
     }


     if( _buffers & FBT_DEPTH )
     {
          _flags |= GL_DEPTH_BUFFER_BIT;

          if( !m_depth_mask )
          {
               glDepthMask( true );
          }

          glClearDepth( _depth );
     }

     if( _buffers & FBT_STENCIL )
     {
          _flags |= GL_STENCIL_BUFFER_BIT;

          if( m_stencil_mask != 0xff )
          {
			            glStencilMask( 0xff );
          }

			       glClearStencil( _stencil );
     }

     if( !_flags )
     {
          return;
     }

		   GLboolean _scissor = glIsEnabled( GL_SCISSOR_TEST );

		   if( !_scissor )
		   {
			       glEnable( GL_SCISSOR_TEST );
		   }

     GLint _viewport[ 4 ], _scissor_box[ 4 ];
     glGetIntegerv( GL_VIEWPORT, _viewport );
		   glGetIntegerv( GL_SCISSOR_BOX, _scissor_box );

     bool _scissor_diff = ( ( _viewport[ 0 ] != _scissor_box[ 0 ] ) || ( _viewport[ 1 ] != _scissor_box[ 1 ] ) || ( _viewport[ 2 ] != _scissor_box[ 2 ] ) || ( _viewport[ 3 ] != _scissor_box[ 3 ] ) );

     if( _scissor_diff )
     {
          glScissor( _viewport[ 0 ], _viewport[ 1 ], _viewport[ 2 ], _viewport[ 3 ] );
     }

     glClear( _flags );

     if( _scissor_diff )
     {
          glScissor( _scissor_box[ 0 ], _scissor_box[ 1 ], _scissor_box[ 2 ], _scissor_box[ 3 ] );
     }

     if( !_scissor )
     {
			       glDisable( GL_SCISSOR_TEST );
     }

     if( _buffers & FBT_STENCIL )
     {
          glStencilMask( m_stencil_mask );
     }

     if( ( _buffers & FBT_DEPTH ) && !m_depth_mask )
     {
          glDepthMask( false );
     }

     if( ( _buffers & FBT_COLOR ) && m_color_mask != CC_RGBA )
     {
          glColorMask( (m_color_mask & CC_RED) != 0, (m_color_mask & CC_GREEN) != 0, (m_color_mask & CC_BLUE) != 0, (m_color_mask & CC_ALPHA) != 0 );
     }
}
//----------------------------------------------------------------------------//
void VideoDriver::_SetColorMask_( uint32 _mask )
{
     _mask &= CC_RGBA;

     if( m_color_mask != _mask )
     {
          m_color_mask = _mask;
          glColorMask( (m_color_mask & CC_RED) != 0, (m_color_mask & CC_GREEN) != 0, (m_color_mask & CC_BLUE) != 0, (m_color_mask & CC_ALPHA) != 0 );
     }
}
//----------------------------------------------------------------------------//
void VideoDriver::_SetFrontFace_( FrontFace _face )
{
     glFrontFace( ( _face == FF_CLOCKWISE ) ? GL_CW : GL_CCW );
}
//----------------------------------------------------------------------------//
void VideoDriver::_SetCullFace_( Face _cull )
{
     if( _cull == F_NONE )
     {
           glDisable( GL_CULL_FACE );
     }
     else
     {
          glEnable( GL_CULL_FACE );
          glCullFace( zxChoose( _cull == F_FRONT, GL_FRONT, zxChoose( _cull == F_BACK, GL_BACK, GL_FRONT_AND_BACK ) ) );
     }
}
//----------------------------------------------------------------------------//
void VideoDriver::_SetPolygonMode_( Face _face, PolygonMode _mode )
{
 zxToDo( "use array" )
     if( _face != F_NONE )
     glPolygonMode( zxChoose( _face == F_FRONT, GL_FRONT, zxChoose( _face == F_BACK, GL_BACK, GL_FRONT_AND_BACK ) ), gl_polygone_mode[ _mode ] ); 
}
//----------------------------------------------------------------------------//
void VideoDriver::_SetPointSize_( uint _size )
{
     // @todo: clamp to max point size
     if( m_point_size != _size )
     {
          m_point_size = _size;
          glPointSize( (float)_size );
     }
}
//----------------------------------------------------------------------------//
void VideoDriver::_SetLineWidth_( uint _width )
{
     // @todo: clamp to max line width
     if( m_line_width != _width )
     {
          m_line_width = _width;
          glLineWidth( (float)_width );
     }
}
//----------------------------------------------------------------------------//
void VideoDriver::_SetProgram_( ProgramPtr _program )
{
      m_current_program = _program;
}
//----------------------------------------------------------------------------//
void VideoDriver::_SetRenderTarget_( RenderTargetPtr _target )
{
     if( m_current_render_target != _target )
     {    
          if( m_current_render_target )
          {
               m_current_render_target -> _Update();
          }

          m_current_render_target = _target;
                    
          if( m_current_render_target )
          {
               m_current_render_target -> _Bind();

               if( m_current_render_target -> m_width != m_current_vp[2] || m_current_render_target -> m_height != m_current_vp[3] )
               {
                    m_current_vp[0] = 0;
                    m_current_vp[1] = 0;
                    m_current_vp[2] = m_current_render_target -> m_width;
                    m_current_vp[3] = m_current_render_target -> m_height;
                    glViewport( m_current_vp[0], m_current_vp[1], m_current_vp[2], m_current_vp[3] );
               }
          }
          else
          {
               glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );
          }
     }
}
//----------------------------------------------------------------------------//
void VideoDriver::_SetBlending_( bool _enabled, BlendFactor _src_factor, BlendFactor _dst_factor, BlendOperation _op )
{
     if( _enabled )
     {
         glEnable( GL_BLEND );
         glBlendFunc( gl_blend_factor[ _src_factor ], gl_blend_factor[ _dst_factor ] );
         glBlendEquation( gl_blend_operation[ _op ] ); 
     }
     else
     {
         glDisable( GL_BLEND );
     }
}
//----------------------------------------------------------------------------//
void VideoDriver::_SetDepthTest_( bool _enabled, bool _write, CompareFunction _fn ) 
{
     m_depth_mask = _write;

     if( _enabled )
     {
          glEnable( GL_DEPTH_TEST );
          glDepthFunc( gl_compare_func[ _fn ] );
     }
     else
     {
          glDisable( GL_DEPTH_TEST );
     }

     glDepthMask( _write );
}
//----------------------------------------------------------------------------//
void VideoDriver::_SetAlphaTest_( bool _enabled, CompareFunction _fn, float _ref, bool _alpha_to_coverage )
{
     if( _enabled )
     {
          glEnable( GL_ALPHA_TEST );
          glAlphaFunc( gl_compare_func[ _fn ], _ref );
     }
     else
     {
          glDisable( GL_ALPHA_TEST );
     }

     if( _alpha_to_coverage )
     {
          glEnable( GL_SAMPLE_ALPHA_TO_COVERAGE );
     }
     else
     {
          glDisable( GL_SAMPLE_ALPHA_TO_COVERAGE );
     }
}
//----------------------------------------------------------------------------//
void VideoDriver::_SetStencilTest_( bool _enabled, CompareFunction _fn, uint8 _ref, uint8 _mask, StencilOperation _stencil_fail, StencilOperation _depth_fail, StencilOperation _pass, bool _two_sided_operation )
{
     m_stencil_mask = _mask;

     if( _enabled )
     {
          glEnable( GL_STENCIL_TEST );
     }
     else
     {
          glDisable( GL_STENCIL_TEST );
     }

     if( _two_sided_operation )
     {
          glEnable( GL_STENCIL_TEST_TWO_SIDE_EXT );
          glActiveStencilFaceEXT( GL_BACK );
          glStencilMask( _mask );
          glStencilFunc( gl_compare_func[ _fn ], _ref, _mask );
          glStencilOp( gl_back_stencil_op[ _stencil_fail ], gl_back_stencil_op[ _depth_fail ], gl_back_stencil_op[ _pass ] );
          glActiveStencilFaceEXT( GL_FRONT );
     }
     else
     {
          glDisable( GL_STENCIL_TEST_TWO_SIDE_EXT );
     }

     glStencilMask( _mask );
     glStencilFunc( gl_compare_func[ _fn ], _ref, _mask );
     glStencilOp( gl_stencil_op[ _stencil_fail ], gl_stencil_op[ _depth_fail ], gl_stencil_op[ _pass ] );
}
//----------------------------------------------------------------------------//
// internal
//----------------------------------------------------------------------------//

void VideoDriver::_BindProgram( ProgramWeakPtr _prog )
{    
     /*
     GLint _current_program = 0;
     glGetIntegerv( GL_CURRENT_PROGRAM, &_current_program );

     if( _prog -> m_program != _current_program )
     {
          glUseProgramObjectARB( _prog -> m_program );
     }*/
     if( m_real_current_program != _prog )
     {
          m_real_current_program = _prog;
          if( m_real_current_program )
          {
               glUseProgramObjectARB( m_real_current_program -> m_program );
          }
          else
          {
               glUseProgramObjectARB( 0 );
          }
     }
}

void VideoDriver::_ActivateTextureUnit( int _unit )
{
     if( m_active_texture_unit != _unit )
     {
          m_active_texture_unit = _unit;
          glActiveTexture( GL_TEXTURE1 + _unit );
     }
}
//----------------------------------------------------------------------------//
void VideoDriver::_SetTextureUnitState( uint _unit, TextureType _type, SamplerPtr _sampler, Vector3& _size )
{
     TextureUnitState& _unit_state = m_texture_unit[_unit];

     _ActivateTextureUnit( _unit );

     TexturePtr _texture;

     // choose texture
     if( _sampler && _sampler -> m_texture && _sampler -> m_texture -> m_type == _type )
     {
          _texture = _sampler -> m_texture; 
     }
     else
     {
          _texture = m_warning_texture[_type];
     }

     // bind 
     _texture -> _Bind();

     // write texture size
     _size.x = (float) _texture -> GetWidth();
     _size.y = (float) _texture -> GetHeight();
     _size.z = (float) _texture -> GetDepth();

     // setup
     if( _sampler )
     {
          // wrap s
          if( _unit_state.wrap_s[_type] != _sampler -> m_wrap_s )
          {
               _unit_state.wrap_s[_type] = _sampler -> m_wrap_s;
               glTexParameteri( gl_texture_type[_type], GL_TEXTURE_WRAP_S, gl_wrap[ _sampler -> m_wrap_s ] );
          }

          // wrap t
          if( _unit_state.wrap_t[_type] != _sampler -> m_wrap_t )
          {
               _unit_state.wrap_t[_type] = _sampler -> m_wrap_t;
               glTexParameteri( gl_texture_type[_type], GL_TEXTURE_WRAP_T, gl_wrap[ _sampler -> m_wrap_t ] );
          }

          // wrap r (3D,Cube)
          if( _unit_state.wrap_r[_type] != _sampler -> m_wrap_r )
          {
               _unit_state.wrap_r[_type] = _sampler -> m_wrap_r;
               glTexParameteri( gl_texture_type[_type], GL_TEXTURE_WRAP_R, gl_wrap[ _sampler -> m_wrap_r ] );
          }

          //min filter
          if( _unit_state.min_filter[_type] != _sampler -> m_min_filter )
          {
               // filter
               _unit_state.min_filter[_type] = _sampler -> m_min_filter;
               glTexParameteri( gl_texture_type[_type], GL_TEXTURE_MIN_FILTER, gl_filter[ _sampler -> m_min_filter ][ zxChoose( _type == TT_RECT, _MIN_FILTER_RECT, _MIN_FILTER ) ] );

               // anisotropy
               int _anisotropy = zxChoose( _sampler -> m_min_filter == TF_ANISOTROPIC, m_max_anisotropy, 1 );
               if( _unit_state.anisotropy[_type] != _anisotropy )
               {
                    _unit_state.anisotropy[_type] = _anisotropy;
                    glTexParameteri( gl_texture_type[_type], GL_TEXTURE_MAX_ANISOTROPY_EXT, _anisotropy );
               }
          }

          //mag filter
          if( _unit_state.mag_filter[_type] != _sampler -> m_mag_filter )
          {
               _unit_state.mag_filter[_type] = _sampler -> m_mag_filter;
               glTexParameteri( gl_texture_type[_type], GL_TEXTURE_MAG_FILTER, gl_filter[ _sampler -> m_mag_filter ][ _MAG_FILTER ] );
          }
     }
     else
     {
          _unit_state.anisotropy[_type] = 1;
          _unit_state.wrap_s[_type] = TW_WRAP;
          _unit_state.wrap_t[_type] = TW_WRAP;
          _unit_state.wrap_r[_type] = TW_WRAP;
          _unit_state.min_filter[_type] = TF_BILINEAR;
          _unit_state.mag_filter[_type] = TF_LINEAR;

          glTexParameteri( gl_texture_type[_type], GL_TEXTURE_WRAP_S, gl_wrap[ _unit_state.wrap_s[_type] ] );
          glTexParameteri( gl_texture_type[_type], GL_TEXTURE_WRAP_T, gl_wrap[ _unit_state.wrap_t[_type] ] );
          glTexParameteri( gl_texture_type[_type], GL_TEXTURE_WRAP_R, gl_wrap[ _unit_state.wrap_r[_type] ] );
          glTexParameteri( gl_texture_type[_type], GL_TEXTURE_MAX_ANISOTROPY_EXT, _unit_state.anisotropy[_type] );
          glTexParameteri( gl_texture_type[_type], GL_TEXTURE_MIN_FILTER, gl_filter[ _unit_state.min_filter[_type] ][ zxChoose( _type == TT_RECT, _MIN_FILTER_RECT, _MIN_FILTER ) ] );
          glTexParameteri( gl_texture_type[_type], GL_TEXTURE_MAG_FILTER, gl_filter[ _unit_state.mag_filter[_type] ][ _MAG_FILTER ] );
     }
}
//----------------------------------------------------------------------------//
void VideoDriver::_EnablePointSprites( uint _unit, bool _enabled )
{
     if( m_texture_unit[_unit].point_sprites_enabled != _enabled )
     {
          _ActivateTextureUnit( _unit );
          m_texture_unit[_unit].point_sprites_enabled = _enabled;
          glTexEnvi( GL_POINT_SPRITE, GL_COORD_REPLACE, _enabled );
     }
}
//----------------------------------------------------------------------------//
bool VideoDriver::_BeforeRendering( PrimitiveType _type, uint _vertex_format )
{
     // render target
     if( m_current_render_target )
     {
          m_current_render_target -> _Validate();

          if( !m_current_render_target -> IsValid() )
          {
               glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );
               return false;
          }

          m_current_render_target -> _Bind();

          if( m_current_render_target -> m_width != m_current_vp[2] || m_current_render_target -> m_height != m_current_vp[3] )
          {
               m_current_vp[0] = 0;
               m_current_vp[1] = 0;
               m_current_vp[2] = m_current_render_target -> m_width;
               m_current_vp[3] = m_current_render_target -> m_height;
               glViewport( m_current_vp[0], m_current_vp[1], m_current_vp[2], m_current_vp[3] );
          }
     }
     else
     {
          GLint _current_target = 0;
          glGetIntegerv( GL_FRAMEBUFFER_BINDING_EXT, &_current_target );

          if( _current_target != 0 )
          {
               glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 ); // render to main window
          }

          if( m_current_vp[0] != m_main_vp[0] || m_current_vp[1] != m_main_vp[1] || m_current_vp[2] != m_main_vp[2] || m_current_vp[3] != m_main_vp[3] )
          {
               m_current_vp[0] = m_main_vp[0];
               m_current_vp[1] = m_main_vp[1];
               m_current_vp[2] = m_main_vp[2];
               m_current_vp[3] = m_main_vp[3];
               glViewport( m_current_vp[0], m_current_vp[1], m_current_vp[2], m_current_vp[3] );
          }
     }

     bool _valid_program = m_current_program && m_current_program -> IsValid();

          
     // reset unused texture units  (-> EndScene)
     uint _prev_used_texture_units = m_used_texture_units;
     uint m_used_texture_units = zxChoose( _valid_program, m_current_program -> m_count_samplers, 0 );
     for( uint i = m_used_texture_units; i < _prev_used_texture_units; ++ i )
     {
          m_texture_unit[ i ].texture = 0;     
     }

     // current program
     if( _valid_program )
     {
          _BindProgram( m_current_program );
     }
     else
     {
          return false;
     }

          
     bool _enable_point_sprites = _type == PT_POINT_SPRITES;

     // synchronize uniforms & enable point sprites
     for( uint i = 0; i < m_current_program -> m_uniforms.size(); ++ i )
     {
          Uniform* _u = m_current_program -> m_uniforms[i];
               
          _u -> _Synchronize();

          if( _u -> m_type == UT_SAMPLER )
          {
               _EnablePointSprites( _u -> m_texture_unit, _enable_point_sprites );
          }
     }

     // reset active texture unit
     _ActivateTextureUnit( -1 );

     // point sprites
     if( m_point_sprites_enabled != _enable_point_sprites )
     {
          if( (m_point_sprites_enabled = _enable_point_sprites) )
          {
               glEnable( GL_VERTEX_PROGRAM_POINT_SIZE_ARB );
               glEnable( GL_POINT_SPRITE_ARB );
          }
          else
          {
               glDisable( GL_VERTEX_PROGRAM_POINT_SIZE_ARB );
               glDisable( GL_POINT_SPRITE );
          }
     }

     return true;
}
//----------------------------------------------------------------------------//
void VideoDriver::_AfterRendering( PrimitiveType _type )
{
}
//----------------------------------------------------------------------------//
// rendering
//----------------------------------------------------------------------------//
void VideoDriver::_Render_( PrimitiveType _type, VertexBufferPtr _vertices, uint _count_of_vertices, uint _first_vertex )
{
     if( !_BeforeRendering( _type ) || !_vertices )
     {
          return;
     }

     if( m_2d_mode )
     {
          glEnableVertexAttribArray( VA_SPECULAR );
          glEnableVertexAttribArray( VA_BINORMAL );
          glEnableVertexAttribArray( VA_TANGENT );
          glEnableVertexAttribArray( VA_NORMAL );
          m_2d_mode = false;
     }

     _first_vertex *= sizeof( Vertex );

     _vertices -> _Bind();

     glVertexAttribPointer( VA_SPECULAR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, specular )) );  
     glVertexAttribPointer( VA_DIFFUSE, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, diffuse )) );  
     glVertexAttribPointer( VA_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, texcoord )) );  
     glVertexAttribPointer( VA_BINORMAL, 3, GL_FLOAT, GL_FALSE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, binormal )) );  
     glVertexAttribPointer( VA_TANGENT, 3, GL_FLOAT, GL_FALSE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, tangent )) );  
     glVertexAttribPointer( VA_NORMAL, 3, GL_FLOAT, GL_FALSE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, normal )) );  
     glVertexAttribPointer( VA_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, position )) );  

     glDrawArrays( gl_primitive_type[ _type ], 0, _count_of_vertices );

     //_vertices -> _Unbind();

     _AfterRendering( _type );
}
//----------------------------------------------------------------------------//
void VideoDriver::_Render_( PrimitiveType _type, VertexBufferPtr _vertices, IndexBufferPtr _indices, uint _count_of_indices, uint _first_vertex, uint _first_index )
{
     if( !_BeforeRendering( _type ) || !_vertices || !_indices )
     {
          return;
     }

     if( m_2d_mode )
     {
          glEnableVertexAttribArray( VA_SPECULAR );
          glEnableVertexAttribArray( VA_BINORMAL );
          glEnableVertexAttribArray( VA_TANGENT );
          glEnableVertexAttribArray( VA_NORMAL );
          m_2d_mode = false;
     }

     _first_vertex *= sizeof( Vertex );

     _vertices -> _Bind();

     glVertexAttribPointer( VA_SPECULAR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, specular )) );  
     glVertexAttribPointer( VA_DIFFUSE, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, diffuse )) );  
     glVertexAttribPointer( VA_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, texcoord )) );  
     glVertexAttribPointer( VA_BINORMAL, 3, GL_FLOAT, GL_FALSE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, binormal )) );  
     glVertexAttribPointer( VA_TANGENT, 3, GL_FLOAT, GL_FALSE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, tangent )) );  
     glVertexAttribPointer( VA_NORMAL, 3, GL_FLOAT, GL_FALSE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, normal )) );  
     glVertexAttribPointer( VA_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, position )) );  

     _indices -> _Bind();
          
     // draw
     glDrawElements( gl_primitive_type[ _type ], _count_of_indices, GL_UNSIGNED_INT, (const GLvoid*)(_first_index) );

     //_indices -> _Unbind();

     //_vertices -> _Unbind();


     _AfterRendering( _type );
}
//----------------------------------------------------------------------------//
void VideoDriver::_Render_( PrimitiveType _type, const Vertex* _vertices, uint _count_of_vertices, uint _first_vertex )
{
     if( !_BeforeRendering( _type ) || !_vertices )
     {
          return;
     }

     if( m_2d_mode )
     {
          glEnableVertexAttribArray( VA_SPECULAR );
          glEnableVertexAttribArray( VA_BINORMAL );
          glEnableVertexAttribArray( VA_TANGENT );
          glEnableVertexAttribArray( VA_NORMAL );
          m_2d_mode = false;
     }

     _first_vertex *= sizeof( Vertex );
     _first_vertex += (uint)_vertices;

     glBindBuffer( GL_ARRAY_BUFFER, 0 );

     glVertexAttribPointer( VA_SPECULAR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, specular )) );  
     glVertexAttribPointer( VA_DIFFUSE, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, diffuse )) );  
     glVertexAttribPointer( VA_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, texcoord )) );  
     glVertexAttribPointer( VA_BINORMAL, 3, GL_FLOAT, GL_FALSE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, binormal )) );  
     glVertexAttribPointer( VA_TANGENT, 3, GL_FLOAT, GL_FALSE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, tangent )) );  
     glVertexAttribPointer( VA_NORMAL, 3, GL_FLOAT, GL_FALSE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, normal )) );  
     glVertexAttribPointer( VA_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, position )) );  

     glDrawArrays( gl_primitive_type[ _type ], 0, _count_of_vertices );

     _AfterRendering( _type );
}
//----------------------------------------------------------------------------//
void VideoDriver::_Render_( PrimitiveType _type, const Vertex* _vertices, const uint32* _indices, uint _count_of_indices, uint _first_vertex, uint _first_index )
{
     if( !_BeforeRendering( _type ) || !_vertices || !_indices )
     {
          return;
     }

     if( m_2d_mode )
     {
          glEnableVertexAttribArray( VA_SPECULAR );
          glEnableVertexAttribArray( VA_BINORMAL );
          glEnableVertexAttribArray( VA_TANGENT );
          glEnableVertexAttribArray( VA_NORMAL );
          m_2d_mode = false;
     }

     _first_vertex *= sizeof( Vertex );
     _first_vertex += (uint)_vertices;
     _first_index += (uint)_indices;

     glBindBuffer( GL_ARRAY_BUFFER, 0 );

     glVertexAttribPointer( VA_SPECULAR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, specular )) );  
     glVertexAttribPointer( VA_DIFFUSE, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, diffuse )) );  
     glVertexAttribPointer( VA_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, texcoord )) );  
     glVertexAttribPointer( VA_BINORMAL, 3, GL_FLOAT, GL_FALSE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, binormal )) );  
     glVertexAttribPointer( VA_TANGENT, 3, GL_FLOAT, GL_FALSE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, tangent )) );  
     glVertexAttribPointer( VA_NORMAL, 3, GL_FLOAT, GL_FALSE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, normal )) );  
     glVertexAttribPointer( VA_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( Vertex ), (const GLvoid*)(_first_vertex + offsetof( Vertex, position )) );  
    
     glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
          
     // draw
     glDrawElements( gl_primitive_type[ _type ], _count_of_indices, GL_UNSIGNED_INT, (const GLvoid*)(_first_index) );


     _AfterRendering( _type );
}
//----------------------------------------------------------------------------//
void VideoDriver::_Render_( PrimitiveType _type, const vector< Vertex >& _vertices, uint _count_of_vertices, uint _first_vertex )
{
     if( !_vertices.empty() )
     {
          _Render_( _type, &_vertices[0], _count_of_vertices, _first_vertex );
     }
}
//----------------------------------------------------------------------------//
void VideoDriver::_Render_( PrimitiveType _type, const vector< Vertex >& _vertices, const vector< uint32 >& _indices, uint _count_of_indices, uint _first_vertex, uint _first_index )
{
     if( !_vertices.empty() && !_indices.empty() )
     {
          _Render_( _type, &_vertices[0], &_indices[0], _count_of_indices, _first_vertex, _first_index );
     }
}
//----------------------------------------------------------------------------//
void VideoDriver::_Render_( PrimitiveType _type, const Vertex2D* _vertices, uint _count_of_vertices, uint _first_vertex )
{
     if( !_BeforeRendering( _type ) || !_vertices )
     {
          return;
     }

     if( !m_2d_mode )
     {
          glDisableVertexAttribArray( VA_SPECULAR );
          glDisableVertexAttribArray( VA_BINORMAL );
          glDisableVertexAttribArray( VA_TANGENT );
          glDisableVertexAttribArray( VA_NORMAL );
          m_2d_mode = true;
     }

     _first_vertex *= sizeof( Vertex );
     _first_vertex += (uint)_vertices;

     glBindBuffer( GL_ARRAY_BUFFER, 0 );

     glVertexAttribPointer( VA_DIFFUSE, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof( Vertex2D ), (const GLvoid*)(_first_vertex + offsetof( Vertex2D, diffuse )) );  
     glVertexAttribPointer( VA_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof( Vertex2D ), (const GLvoid*)(_first_vertex + offsetof( Vertex2D, texcoord )) );  
     glVertexAttribPointer( VA_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( Vertex2D ), (const GLvoid*)(_first_vertex + offsetof( Vertex2D, position )) );  

     glDrawArrays( gl_primitive_type[ _type ], 0, _count_of_vertices );


     _AfterRendering( _type );
}
//----------------------------------------------------------------------------//
void VideoDriver::_Render_( PrimitiveType _type, const Vertex2D* _vertices, const uint32* _indices, uint _count_of_indices, uint _first_vertex, uint _first_index )
{
     if( !_BeforeRendering( _type ) || !_vertices || !_indices )
     {
          return;
     }

     if( !m_2d_mode )
     {
          glDisableVertexAttribArray( VA_SPECULAR );
          glDisableVertexAttribArray( VA_BINORMAL );
          glDisableVertexAttribArray( VA_TANGENT );
          glDisableVertexAttribArray( VA_NORMAL );
          m_2d_mode = true;
     }

     _first_vertex *= sizeof( Vertex );
     _first_vertex += (uint)_vertices;
     _first_index += (uint)_indices;

     glBindBuffer( GL_ARRAY_BUFFER, 0 );

     glVertexAttribPointer( VA_DIFFUSE, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof( Vertex2D ), (const GLvoid*)(_first_vertex + offsetof( Vertex2D, diffuse )) );  
     glVertexAttribPointer( VA_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof( Vertex2D ), (const GLvoid*)(_first_vertex + offsetof( Vertex2D, texcoord )) );  
     glVertexAttribPointer( VA_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( Vertex2D ), (const GLvoid*)(_first_vertex + offsetof( Vertex2D, position )) );  
    
     glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
          
     // draw
     glDrawElements( gl_primitive_type[ _type ], _count_of_indices, GL_UNSIGNED_INT, (const GLvoid*)(_first_index) );


     _AfterRendering( _type );
}
//----------------------------------------------------------------------------//
void VideoDriver::_Render_( PrimitiveType _type, const vector< Vertex2D >& _vertices, uint _count_of_vertices, uint _first_vertex )
{
     if( !_vertices.empty() )
     {
          _Render_( _type, &_vertices[0], _count_of_vertices, _first_vertex );
     }
}
//----------------------------------------------------------------------------//
void VideoDriver::_Render_( PrimitiveType _type, const vector< Vertex2D >& _vertices, const vector< uint32 >& _indices, uint _count_of_indices, uint _first_vertex, uint _first_index )
{
     if( !_vertices.empty() && !_indices.empty() )
     {
          _Render_( _type, &_vertices[0], &_indices[0], _count_of_indices, _first_vertex, _first_index );
     }
}
//----------------------------------------------------------------------------//
void VideoDriver::_RenderFullscreenQuad_( void )
{
     _Render_( PT_TRIANGLE_STRIP, m_fsquad, 4 );
}
//----------------------------------------------------------------------------//
// main
//----------------------------------------------------------------------------//
void VideoDriver::_BeginScene_( void )
{
}
//----------------------------------------------------------------------------//
void VideoDriver::_EndScene_( void )
{
     SwapBuffers( wglGetCurrentDC() );
}
//----------------------------------------------------------------------------//
void VideoDriver::_SetMainViewport_( int _px, int _py, int _sx, int _sy )
{
     m_main_vp[ 0 ] = _px;
     m_main_vp[ 1 ] = _py;
     m_main_vp[ 2 ] = _sx;
     m_main_vp[ 3 ] = _sy;
          
     m_ortho2d.MakeOrtho2D( m_main_vp[2], m_main_vp[3] );

     if( !m_current_render_target )
     {
          m_current_vp[ 0 ] = m_main_vp[ 0 ];
          m_current_vp[ 1 ] = m_main_vp[ 1 ];
          m_current_vp[ 2 ] = m_main_vp[ 2 ];
          m_current_vp[ 3 ] = m_main_vp[ 3 ];

          glViewport( m_current_vp[ 0 ], m_current_vp[ 1 ], m_current_vp[ 2 ], m_current_vp[ 3 ] );
     }
}
//----------------------------------------------------------------------------//

} // namespace zx
